﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using WiMo.Games;
using WiMo.Games.Drawables;

namespace WiMo.Games
{
    public class Quadrant : IDisposable
    {
        private const int INT_MAX_CHILDREN = 8;
        private const int INT_MAX_LEVELS = 5;

        public int ChildrenPerQuandrant{get; set;}

        public Quadrant()
        {
            Sprites = new List<IDrawable>();
            ChildrenPerQuandrant = INT_MAX_CHILDREN;
        }

        public Quadrant(Quadrant parent, Rectangle rect) : this()
        {
            Parent = parent;
            BoundingRect = rect;
            Level = Parent.Level + 1;
        }

        public static void Init(Size size)
        {
            Root = new Quadrant();
            Root.Level = 1;
            Root.BoundingRect = new Rectangle(0, 0, size.Width, size.Height);            
        }

        public static Quadrant Root
        {
            get;
            set;
        }

        public int Level { get; set; }

        public Rectangle BoundingRect { get; set; }
                                                    
        public Quadrant Parent { get; private set; }
        public Quadrant NW { get; private set; }
        public Quadrant NE { get; private set; }
        public Quadrant SW { get; private set; }
        public Quadrant SE { get; private set; }

        public List<IDrawable> Sprites { get; private set; }

        public void CreateChildQuandrants()
        {
            NW = new Quadrant(this, new Rectangle(BoundingRect.Left, BoundingRect.Top, BoundingRect.Width / 2, BoundingRect.Height / 2)) { ChildrenPerQuandrant = this.ChildrenPerQuandrant };
            NE = new Quadrant(this, new Rectangle(NW.BoundingRect.Width + BoundingRect.Left, BoundingRect.Top, BoundingRect.Width / 2, BoundingRect.Height / 2)) { ChildrenPerQuandrant = this.ChildrenPerQuandrant };
            SW = new Quadrant(this, new Rectangle(BoundingRect.Left, BoundingRect.Top + NE.BoundingRect.Height, BoundingRect.Width / 2, BoundingRect.Height / 2)) { ChildrenPerQuandrant = this.ChildrenPerQuandrant };
            SE = new Quadrant(this, new Rectangle(NW.BoundingRect.Width + BoundingRect.Left, BoundingRect.Top + NW.BoundingRect.Height, BoundingRect.Width / 2, BoundingRect.Height / 2)) { ChildrenPerQuandrant = this.ChildrenPerQuandrant };
            
        }

        private bool ContainsSprite(IDrawable sprite)
        {
            var unScaledBound = new Rectangle(sprite.UnscaledPosition.X, sprite.UnscaledPosition.Y, sprite.UnscaledSize.Width, sprite.UnscaledSize.Height);

            return BoundingRect.IntersectsWith(unScaledBound);
        }

        private void DisownChildren()
        {
            foreach (var child in Sprites)
            {
                child.Quandrants.Remove(this);
                AddToChildren(child);
            }

            Sprites.Clear();
        }

        public bool HasChildren
        {
            get { return NW != null; }
        }

        private void Adopt(IDrawable sprite)
        {
            if(ContainsSprite(sprite))
                AddChild(sprite);
        }

        private void AddToChildren(IDrawable sprite)
        {
            if(NW.ContainsSprite(sprite))
                NW.AddChild(sprite);

            if (NE.ContainsSprite(sprite))
                NE.AddChild(sprite);

            if (SW.ContainsSprite(sprite))
                SW.AddChild(sprite);

            if (SE.ContainsSprite(sprite))
                SE.AddChild(sprite);
        }

        private void AddToThis(IDrawable sprite)
        {
            if (Sprites.Count < ChildrenPerQuandrant || Level > INT_MAX_LEVELS)
            {
                Sprites.Add(sprite);
                sprite.Quandrants.Add(this);
                IsClean = false;
            }
            else
            {
                CreateChildQuandrants();
                DisownChildren();                
                AddToChildren(sprite);
            }
        }

        public void AddChild(IDrawable sprite)
        {
            if (HasChildren)
                AddToChildren(sprite);
            else
                AddToThis(sprite);
        }

        private void GiveBackToParent()
        {
            foreach (var sprite in Sprites)
            {
                Parent.Sprites.Add(sprite);
                sprite.Quandrants.Remove(this);
                sprite.Quandrants.Add(Parent);
            }
        }

        private void CollapseIfNecessary()
        {
            if ((NW.SpriteCount +
                NE.SpriteCount +
                SW.SpriteCount +
                SE.SpriteCount) <= ChildrenPerQuandrant)
            {
                NW.Dispose();
                NE.Dispose();
                SE.Dispose();
                SW.Dispose();

                NW = null;
                NE = null;
                SE = null;
                SW = null;

                IsClean = false;

                if (Parent != null)
                    Parent.CollapseIfNecessary();
            }
        }

        public int SpriteCount
        {
            get
            {
                if (HasChildren)
                    return NE.SpriteCount + NW.SpriteCount + SE.SpriteCount + SW.SpriteCount;
                else
                    return Sprites.Count;
            }
        }

        public void Remove(IDrawable sprite)
        {
            foreach (var quad in sprite.Quandrants)
            {
                quad.Sprites.Remove(sprite);
                if(quad.Parent != null)
                    quad.Parent.CollapseIfNecessary();

                quad.IsClean = false;
            }

            sprite.Quandrants.Clear();
        }

        public bool IsClean { get; set; }

        public void MarkClean()
        {
            if (HasChildren)
            {
                NE.MarkClean();
                NW.MarkClean();
                SE.MarkClean();
                SW.MarkClean();
            }

            IsClean = true;
        }

        public void Dispose()
        {            
            GiveBackToParent();
        }
    }
}
